package com.millstein.tsinglog.service.impl;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.millstein.tsinglog.commons.constant.SystemConstant;
import com.millstein.tsinglog.commons.enums.ResultCode;
import com.millstein.tsinglog.exception.TsinglogException;
import com.millstein.tsinglog.commons.service.BaseService;
import com.millstein.tsinglog.mapper.biz.*;
import com.millstein.tsinglog.utils.*;
import com.millstein.tsinglog.model.dto.article.ArticleSaveDTO;
import com.millstein.tsinglog.model.pojo.*;
import com.millstein.tsinglog.model.vo.biz.ArticleVo;
import com.millstein.tsinglog.model.vo.result.PageDetail;
import com.millstein.tsinglog.model.vo.result.PageResult;
import com.millstein.tsinglog.service.ArticleService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import tk.mybatis.mapper.entity.Example;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
@Slf4j
public class ArticleServiceImpl extends BaseService implements ArticleService {

    @Autowired
    private ArticleMapper articleMapper;

    @Autowired
    private ArticleContentMapper articleContentMapper;

    @Autowired
    private CategoryMapper categoryMapper;

    @Autowired
    private CarouselImageMapper carouselImageMapper;

    @Autowired
    private CommentMapper commentMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public PageResult<ArticleVo> getArticleByPage(Integer currentPage, Integer pageSize, String keyword, Long cid) {
        // 1.对参数进行校验
        if (currentPage == null) {
            currentPage = SystemConstant.DEFAULT_CURRENT_PAGE;
        }
        if (pageSize == null) {
            pageSize = SystemConstant.DEFAULT_PAGE_SIZE;
        }

        Example example = new Example(Article.class);
        Example.Criteria criteria = example.createCriteria();

        // 2.条件搜索
        if (StringUtils.isNotBlank(keyword)) {
            criteria.andLike("title", "%" + keyword + "%");
        }
        if (cid != null) {
            criteria.andEqualTo("categoryId", cid);
        }
        // 3.过滤只查询状态为正常的记录
        criteria.andEqualTo("status", SystemConstant.STATUS_ENTITY_NORMAL);
        // 4.按照修改时间的逆序排序
        example.setOrderByClause("update_time desc");
        // 5.分页查询
        PageHelper.startPage(currentPage, pageSize);
        List<Article> list = articleMapper.selectByExample(example);
        if (CollectionUtils.isEmpty(list)) {
            throw new TsinglogException(ResultCode.RETURN_DATA_NOT_FOUND);
        }
        PageInfo<Article> pageInfo = new PageInfo<Article>(list);
        // 6.将List<Article>转为List<ArticleVo>
        List<ArticleVo> listVo = this.transListArticleToListArticleVo(list, true);
        return new PageResult<ArticleVo>(pageInfo.getTotal(), pageInfo.getPages(), listVo);
    }


    @Override
    @Transactional
    public void changeArticleStatus(Long id, Short isRelease, Short canComment, Short upToTop) {
        this.changeArticleStatusLogic(id, isRelease, canComment, upToTop);

        // 清除redis中对应的数据
        redisTemplate.delete(SystemConstant.REDIS_KEY_FOR_LATEST_RELEASE_ARTICLE);
        redisTemplate.delete(SystemConstant.REDIS_KEY_FOR_LATEST_REPLY_ARTICLE);
        redisTemplate.delete(SystemConstant.REDIS_KEY_FOR_HOT_ARTICLE);
    }

    /**
     * 这个方法是处理修改文章展示状态的逻辑的，
     * 对比changeArticleStatus和changeMultiArticleStatus方法，后两者还需要在修改后对redis进行操作
     * @param id 文章id
     * @param isRelease 是否发布
     * @param canComment 能否评论
     * @param upToTop 是否置顶
     */
    private void changeArticleStatusLogic(Long id, Short isRelease, Short canComment, Short upToTop) {
//        if (id == null) {
//            throw new TsinglogException(ResultCode.PARAM_IS_BLANK);
//        }
        Optional.ofNullable(id).orElseThrow(() -> new TsinglogException(ResultCode.PARAM_IS_BLANK));

        if (isRelease == null && canComment == null && upToTop == null) {
            throw new TsinglogException(ResultCode.PARAM_IS_BLANK);
        }

        Article article = articleMapper.selectArticleByPrimaryKeyForServer(id);
        Optional.ofNullable(article).orElseThrow(() -> new TsinglogException(ResultCode.RETURN_DATA_NOT_FOUND));
//        if (article == null) {
//            throw new TsinglogException(ResultCode.RETURN_DATA_NOT_FOUND);
//        }

        // 如果原文章的状态是已发布，现在的状态是未发布，而且原文章在轮播图中有显示，那么就需要将轮播图中的该条记录设置为未启用
        CarouselImage criteria = new CarouselImage();
        criteria.setAid(id);
        criteria.setStatus(SystemConstant.STATUS_ENTITY_NORMAL);
        CarouselImage image = carouselImageMapper.selectOne(criteria);
        if (article.getIsRelease() == SystemConstant.USABLE_ENTITY_ON
                && isRelease == SystemConstant.USABLE_ENTITY_OFF
                && image.getUsable() == SystemConstant.USABLE_ENTITY_ON) {
            image.setUsable(SystemConstant.USABLE_ENTITY_OFF);
            image.setUpdateTime(getCurrentDateStr());
            int res = carouselImageMapper.updateByPrimaryKey(image);
            if (res != 1) {
                throw new TsinglogException(ResultCode.UPDATE_FAILURE);
            }
        }

        if (isRelease != null) {
            article.setIsRelease(isRelease);
            if (isRelease == 0) {
                article.setReleaseTime(SystemConstant.ARTICLE_IS_NOT_RELEASED_MESSAGE);
            } else {
                article.setReleaseTime(getCurrentDateStr());
            }
        }

        if (canComment != null) {
            article.setCanComment(canComment);
        }
        if (upToTop != null) {
            article.setUpToTop(upToTop);
        }

        article.setUpdateTime(getCurrentDateStr());
        int res = articleMapper.updateByPrimaryKeySelective(article);
        if (res != 1) {
            throw new TsinglogException(ResultCode.UPDATE_FAILURE);
        }
    }

    @Override
    @Transactional
    public void changeMultiArticleStatus(List<Long> ids, Short isRelease, Short canComment, Short upToTop) {
        if (CollectionUtils.isEmpty(ids)) {
            throw new TsinglogException(ResultCode.PARAM_IS_BLANK);
        }
        for (Long id: ids) {
            this.changeArticleStatusLogic(id, isRelease, canComment, upToTop);
        }

        // 清除redis中对应的数据
        redisTemplate.delete(SystemConstant.REDIS_KEY_FOR_LATEST_RELEASE_ARTICLE);
        redisTemplate.delete(SystemConstant.REDIS_KEY_FOR_LATEST_REPLY_ARTICLE);
        redisTemplate.delete(SystemConstant.REDIS_KEY_FOR_HOT_ARTICLE);
    }

    @Override
    @Transactional
    public void insertArticle(ArticleSaveDTO articleSaveDTO) {

        // 1.向biz_article中保存数据
        Article article = new Article();
        article.setTitle(articleSaveDTO.getArticleTitle());
        article.setCategoryId(articleSaveDTO.getSelectedCategory());
        article.setType(articleSaveDTO.getSelectedType());
        article.setIsRelease(articleSaveDTO.getRelease());
        // 分情况设置发布时间
        if (articleSaveDTO.getRelease() == SystemConstant.ARTICLE_IS_RELEASED) {
            article.setReleaseTime(getCurrentDateStr());
        } else {
            article.setReleaseTime(SystemConstant.ARTICLE_IS_NOT_RELEASED_MESSAGE);
        }
        article.setCanComment(SystemConstant.ARTICLE_CAN_COMMENT); // 默认设置文章允许评论
        article.setUpToTop(SystemConstant.ARTICLE_IS_NOT_UP_TO_TOP); // 默认设置文章不置顶
        article.setStatus(SystemConstant.STATUS_ENTITY_NORMAL);
        article.setCreateTime(getCurrentDateStr());
        article.setUpdateTime(getCurrentDateStr());

        int result = articleMapper.insert(article);

        // 2.向biz_article_content中保存数据
        ArticleContent articleContent = new ArticleContent();
        articleContent.setArticleId(article.getId());
        articleContent.setCoverImage(articleSaveDTO.getSelectedCoverUrl());
        articleContent.setEditorType(articleSaveDTO.getEditorType());
        articleContent.setArticleSummary(articleSaveDTO.getSummary());
        articleContent.setArticleContent(articleSaveDTO.getArticleContent());

        result += articleContentMapper.insert(articleContent);

        // 3.向biz_article_tag中保存数据
        List<Long> ids = articleSaveDTO.getSelectedTag();
        for (Long tagId: ids) {
            result += articleMapper.insertArticleAndTag(article.getId(), tagId, getCurrentDateStr());
        }

        // 4.对操作结果进行判断
        if (result != (2 + ids.size())) {
            throw new TsinglogException(ResultCode.INSERT_FAILURE);
        }

        // 5.根据optionFlag判断是否要对轮播图也进行操作，新增操作只会涉及到optionFlag为1的情况
        if (articleSaveDTO.getOptionFlag() == 1) {
            this.saveCarouselImageForArticleSaving(article.getId(), articleSaveDTO.getSelectedCoverUrl(), getCurrentDateStr());
        }

        // 6.如果新增的文章是发布状态，需要将原redis中的数据删除
        if (articleSaveDTO.getRelease() == SystemConstant.ARTICLE_IS_RELEASED) {
            redisTemplate.delete(SystemConstant.REDIS_KEY_FOR_LATEST_RELEASE_ARTICLE);
            redisTemplate.delete(SystemConstant.REDIS_KEY_FOR_LATEST_REPLY_ARTICLE);
        }

    }

    /**
     * 保存文章的同时保存轮播图的方法
     * @param aid 文章id
     * @param coverUrl 文章封面
     * @param dateStr 操作时间
     */
    private void saveCarouselImageForArticleSaving(Long aid, String coverUrl, String dateStr) {
        CarouselImage image = new CarouselImage();
        image.setType(SystemConstant.LOCAL_ARTICLE);
        image.setAid(aid);
        image.setPicUrl(coverUrl);
        image.setSort(SystemConstant.FIRST_PRIORITY);
        image.setUsable(SystemConstant.USABLE_ENTITY_ON);
        image.setStatus(SystemConstant.STATUS_ENTITY_NORMAL);
        image.setCreateTime(dateStr);
        image.setUpdateTime(dateStr);

        int result = carouselImageMapper.insert(image);
        if (result != 1) {
            throw new TsinglogException(ResultCode.INSERT_FAILURE);
        }

        redisTemplate.delete(SystemConstant.REDIS_KEY_FOR_CAROUSEL_IMAGE_LIST);
    }

    /**
     * 根据文章id查询对应的文章内容
     * @param id
     * @return
     */
    private ArticleContent getContentByArticleId(Long id) {
        ArticleContent criteria = new ArticleContent();
        criteria.setArticleId(id);
        return articleContentMapper.selectOne(criteria);
    }

    @Override
    public ArticleVo getArticleById(Long id) {
        if (id == null) {
            throw new TsinglogException(ResultCode.PARAM_IS_BLANK);
        }

        ArticleVo vo = null;
        // 1.查询文章相关的信息
        Article article = articleMapper.selectArticleByPrimaryKeyForServer(id);
        // 2.查询文章内容的相关信息
        ArticleContent content = this.getContentByArticleId(id);
        // 3.查询文章相关的标签
        List<Long> listTags = articleMapper.getTagsByArticleId(id);

        // 只要有一个没有查询到就报错
        if (article == null || content == null || CollectionUtils.isEmpty(listTags)) {
            throw new TsinglogException(ResultCode.RETURN_DATA_NOT_FOUND);
        }

        Category category = categoryMapper.selectCategoryByPrimaryKeyForServer(article.getCategoryId());
        if (category == null) {
            throw new TsinglogException(ResultCode.RETURN_DATA_NOT_FOUND);
        }
        vo = new ArticleVo(article);
        // 4.将内容封装给ArticleDetailVo
        vo.setId(id);
        vo.setTitle(article.getTitle());
        vo.setArticleContent(content.getArticleContent());
        vo.setEditorType(content.getEditorType());
        vo.setType(article.getType());
        vo.setCategoryName(category.getCategoryName());
        vo.setIsRelease(article.getIsRelease());
        vo.setCategoryId(article.getCategoryId());
        vo.setSummary(content.getArticleSummary());
        vo.setTags(listTags);
        vo.setCoverImage(content.getCoverImage());
        this.setBrowseAndCommentAndPreferCountForVo(vo);

        // 处理ifExistedInCarousel字段
        CarouselImage criteria = new CarouselImage();
        criteria.setAid(id);
        criteria.setStatus(SystemConstant.STATUS_ENTITY_NORMAL);
        CarouselImage image = carouselImageMapper.selectOne(criteria);
        vo.setIfExistedInCarousel(image != null);

        return vo;
    }

    @Override
    @Transactional
    public void updateArticle(ArticleSaveDTO articleSaveDTO) {

        Long id = articleSaveDTO.getId();
        if (id == null) {
            throw new TsinglogException(ResultCode.RETURN_DATA_NOT_FOUND);
        }

        // 1.查询id对应的文章信息并修改
        Article dbArticle = articleMapper.selectArticleByPrimaryKeyForServer(id);
        if (dbArticle == null) {
            throw new TsinglogException(ResultCode.RETURN_DATA_NOT_FOUND);
        }
        dbArticle.setTitle(articleSaveDTO.getArticleTitle());
        dbArticle.setCategoryId(articleSaveDTO.getSelectedCategory());
        // 如果原来是不发布状态，现在是发布状态，那么要将发布时间设置为现在的时间
        if (dbArticle.getIsRelease() == SystemConstant.ARTICLE_IS_NOT_RELEASED &&
                articleSaveDTO.getRelease() == SystemConstant.ARTICLE_IS_RELEASED) {
            dbArticle.setReleaseTime(getCurrentDateStr());
        } else if (dbArticle.getIsRelease() == SystemConstant.ARTICLE_IS_RELEASED &&
                articleSaveDTO.getRelease() == SystemConstant.ARTICLE_IS_NOT_RELEASED) {
            // 如果原来是发布状态，现在不是发布状态，那么要将发布时间设置为“未发布”
            dbArticle.setReleaseTime(SystemConstant.ARTICLE_IS_NOT_RELEASED_MESSAGE);
        }
        dbArticle.setIsRelease(articleSaveDTO.getRelease());
        dbArticle.setType(articleSaveDTO.getSelectedType());
        dbArticle.setUpdateTime(getCurrentDateStr());
        int result = articleMapper.updateByPrimaryKeySelective(dbArticle);

        // 2.查询id对应的文章内容信息并修改
        ArticleContent dbContent = this.getContentByArticleId(dbArticle.getId());
        if (dbContent == null) {
            throw new TsinglogException(ResultCode.RETURN_DATA_NOT_FOUND);
        }
        dbContent.setArticleId(dbArticle.getId());
        dbContent.setCoverImage(articleSaveDTO.getSelectedCoverUrl());
        dbContent.setArticleContent(articleSaveDTO.getArticleContent());
        dbContent.setArticleSummary(articleSaveDTO.getSummary());
        result += articleContentMapper.updateByPrimaryKeySelective(dbContent);

        // 3.查询id对应的文章标签信息，删除后再新增
        List<Long> listTags = articleMapper.getTagsByArticleId(dbArticle.getId());
        if (CollectionUtils.isEmpty(listTags)) {
            throw new TsinglogException(ResultCode.RETURN_DATA_NOT_FOUND);
        }
        // 3.1.删除原来的记录
        // 查询出删除前的记录数
        List<Long> formerIds = articleMapper.getTagsByArticleId(dbArticle.getId());
        // 需要查询出记录有几条，因为下面删除的时候有几条成功就会返回几
        int count = 0;
        if (!CollectionUtils.isEmpty(formerIds)) {
            count = formerIds.size();
        }
        result += articleMapper.deleteByAid(dbArticle.getId());
        // 3.2.新增现在的记录
        List<Long> ids = articleSaveDTO.getSelectedTag();
        for (Long tagId: ids) {
            result += articleMapper.insertArticleAndTag(dbArticle.getId(), tagId, getCurrentDateStr());
        }

        // 4.对操作结果进行判断
        if (result != (2 + count + ids.size())) {
            throw new TsinglogException(ResultCode.UPDATE_FAILURE);
        }

        // 5.根据optionFlag判断是否要对轮播图也进行操作，更新操作会涉及到optionFlag为1和2两种情况
        if (articleSaveDTO.getOptionFlag() == 1) { // 新增
            this.saveCarouselImageForArticleSaving(dbArticle.getId(), dbContent.getCoverImage(), getCurrentDateStr());
        } else if (articleSaveDTO.getOptionFlag() == 2) { // 修改
            // 先查询原来的信息
            CarouselImage criteria = new CarouselImage();
            criteria.setAid(dbArticle.getId());
            criteria.setStatus(SystemConstant.STATUS_ENTITY_NORMAL);
            CarouselImage dbImage = carouselImageMapper.selectOne(criteria);

            if (dbImage == null) {
                throw new TsinglogException(ResultCode.HANDLE_DATA_NOT_EXISTED);
            }

            dbImage.setPicUrl(dbContent.getCoverImage());
            dbImage.setUsable(SystemConstant.USABLE_ENTITY_ON); // 如果该轮播图没有启用，那么自动启用
            dbImage.setSort(SystemConstant.FIRST_PRIORITY); // 启用的时候自动设置为最高优先级
            dbImage.setUpdateTime(getCurrentDateStr());

            result = carouselImageMapper.updateByPrimaryKey(dbImage);
            if (result != 1) {
                throw new TsinglogException(ResultCode.UPDATE_FAILURE);
            }
        } else if (articleSaveDTO.getOptionFlag() == 3) { // 删除
            // 先查询原来的信息
            CarouselImage criteria = new CarouselImage();
            criteria.setAid(dbArticle.getId());
            criteria.setStatus(SystemConstant.STATUS_ENTITY_NORMAL);
            CarouselImage dbImage = carouselImageMapper.selectOne(criteria);

            if (dbImage == null) {
                throw new TsinglogException(ResultCode.HANDLE_DATA_NOT_EXISTED);
            }

            dbImage.setStatus(SystemConstant.STATUS_ENTITY_DELETE);
            dbImage.setUpdateTime(getCurrentDateStr());

            result = carouselImageMapper.updateByPrimaryKey(dbImage);
            if (result != 1) {
                throw new TsinglogException(ResultCode.DELETE_FAILURE);
            }
        } else {
            throw new TsinglogException(ResultCode.PARAM_IS_INVALID);
        }


        redisTemplate.delete(SystemConstant.REDIS_KEY_FOR_CAROUSEL_IMAGE_LIST);
        redisTemplate.delete(SystemConstant.REDIS_KEY_FOR_LATEST_RELEASE_ARTICLE);
        redisTemplate.delete(SystemConstant.REDIS_KEY_FOR_LATEST_REPLY_ARTICLE);

    }

    @Override
    @Transactional
    public void deleteByIds(List<Long> ids) {

        if (CollectionUtils.isEmpty(ids)) {
            throw new TsinglogException(ResultCode.PARAM_IS_BLANK);
        }

        for (Long id: ids) {
            Article article = articleMapper.selectArticleByPrimaryKeyForServer(id);
            if (article == null) {
                throw new TsinglogException(ResultCode.HANDLE_DATA_NOT_EXISTED);
            }

            article.setStatus(SystemConstant.STATUS_ENTITY_DELETE);
            article.setUpdateTime(getCurrentDateStr());

            int result = articleMapper.updateByPrimaryKeySelective(article);
            if (result != 1) {
                throw new TsinglogException(ResultCode.DELETE_FAILURE);
            }

            // 如果该文章在轮播图中有启用的记录，那么把这条记录也删除
            CarouselImage criteria = new CarouselImage();
            criteria.setAid(id);
            CarouselImage image = carouselImageMapper.selectOne(criteria);
            if (image != null && image.getStatus() != SystemConstant.STATUS_ENTITY_DELETE) {
                image.setStatus(SystemConstant.STATUS_ENTITY_DELETE);
                image.setUpdateTime(getCurrentDateStr());
                result = carouselImageMapper.updateByPrimaryKey(image);
                if (result != 1) {
                    throw new TsinglogException(ResultCode.DELETE_FAILURE);
                }
                redisTemplate.delete(SystemConstant.REDIS_KEY_FOR_CAROUSEL_IMAGE_LIST);
            }

            // 删除该文章的评论数据
            Comment comment = new Comment();
            comment.setStatus(SystemConstant.STATUS_ENTITY_NORMAL);
            comment.setAid(id);
            List<Comment> dbComments = commentMapper.select(comment);
            if (!CollectionUtils.isEmpty(dbComments)) {
                for (Comment c: dbComments) {
                    c.setStatus(SystemConstant.STATUS_ENTITY_DELETE);
                    c.setUpdateTime(getCurrentDateStr());
                    result = commentMapper.updateByPrimaryKey(c);
                    if (result != 1) {
                        throw new TsinglogException(ResultCode.DELETE_FAILURE);
                    }
                }
            }

            // 删除浏览数据
            articleMapper.deleteArticleBrowseById(id);
            // 删除喜欢数据
            articleMapper.deleteArticlePreferById(id);
        }

        // 删除redis中对应的数据
        redisTemplate.delete(SystemConstant.REDIS_KEY_FOR_LATEST_RELEASE_ARTICLE);
        redisTemplate.delete(SystemConstant.REDIS_KEY_FOR_LATEST_REPLY_ARTICLE);
        redisTemplate.delete(SystemConstant.REDIS_KEY_FOR_HOT_ARTICLE);
    }

    @Override
    public PageResult<ArticleVo> getArticleDataForSave(Integer currentPage, String keyword) {

        if (currentPage == null) {
            currentPage = SystemConstant.DEFAULT_CURRENT_PAGE;
        }

        // 1.查询在轮播图中，已经显示过的文章的id集合
        CarouselImage image = new CarouselImage();
        // 查询的范围是本站点的文章
        image.setType(SystemConstant.LOCAL_ARTICLE);
        // 状态不是删除的图片
        image.setStatus(SystemConstant.STATUS_ENTITY_NORMAL);

        List<CarouselImage> list = carouselImageMapper.select(image);
        List<Long> ids = new ArrayList<Long>();
        if (!CollectionUtils.isEmpty(list)) {
            ids = list.stream().map(CarouselImage::getAid).collect(Collectors.toList());
        }

        // 2.分页查询文章数据，查询出来的数据要和ids取交集
        Example example = new Example(Article.class);
        Example.Criteria criteria = example.createCriteria();

        criteria.andNotIn("id", ids);
        criteria.andEqualTo("isRelease", SystemConstant.ARTICLE_IS_RELEASED);
        criteria.andEqualTo("status", SystemConstant.STATUS_ENTITY_NORMAL);
        if (StringUtils.isNotBlank(keyword)) {
            criteria.andLike("title", "%" + keyword + "%");
        }

        example.setOrderByClause("update_time desc");

        PageHelper.startPage(currentPage, SystemConstant.PAGE_SIZE_FOR_CAROUSEL_IMAGE_SAVE_MODAL);
        List<Article> articles = articleMapper.selectByExample(example);
        if (CollectionUtils.isEmpty(articles)) {
            throw new TsinglogException(ResultCode.RETURN_DATA_NOT_FOUND);
        }

        List<ArticleVo> vos = new ArrayList<ArticleVo>();
        for (Article article: articles) {
            ArticleVo vo = new ArticleVo(article);

            ArticleContent content = new ArticleContent();
            content.setArticleId(article.getId());

            List<ArticleContent> contents = articleContentMapper.select(content);
            if (!CollectionUtils.isEmpty(contents)) {
                vo.setCoverImage(contents.get(0).getCoverImage());
            }
//            this.setBrowseAndCommentAndPreferCountForVo(vo);

            vos.add(vo);
        }


        PageInfo pageInfo = new PageInfo(articles);

        return new PageResult<ArticleVo>(pageInfo.getTotal(), pageInfo.getPages(), vos);
    }

    @Override
    public PageResult<ArticleVo> getArticleForLatest(Integer currentPage, Integer pageSize, Short type) {
        if (currentPage == null) {
            currentPage = SystemConstant.DEFAULT_CURRENT_PAGE;
        }
        if (pageSize == null) {
            pageSize = SystemConstant.DEFAULT_PAGE_SIZE;
        }

        ValueOperations operations = redisTemplate.opsForValue();

        PageResult<ArticleVo> latestArticlePage;

        // listMap中的key是数据库列名，而不是实体类的属性名
        List<Article> list = new ArrayList<>();

        if (SystemConstant.INDEX_TYPE_RELEASE == type) { // 最近更新的文章
            if (redisTemplate.hasKey(SystemConstant.REDIS_KEY_FOR_LATEST_RELEASE_ARTICLE)) {
                return (PageResult<ArticleVo>) operations.get(SystemConstant.REDIS_KEY_FOR_LATEST_RELEASE_ARTICLE);
            } else {
                PageHelper.startPage(currentPage, pageSize);
                list = articleMapper.getArticleForLatestRelease();
            }
        } else if  (SystemConstant.INDEX_TYPE_REPLY == type) { // 最近回复的文章
            if (redisTemplate.hasKey(SystemConstant.REDIS_KEY_FOR_LATEST_REPLY_ARTICLE)) {
                return (PageResult<ArticleVo>) operations.get(SystemConstant.REDIS_KEY_FOR_LATEST_REPLY_ARTICLE);
            } else {
                PageHelper.startPage(currentPage, pageSize);
                list = articleMapper.getArticleForLatestReply();
            }
        }

        // 将存放有数据库列名的信息的数据转化为我们需要的List<Article>
//        List<Article> list = new ArrayList<>();
//        for (Map<String, Object> map : listMap) {
//            Map<String, Object> fieldMap = MyUtils.transColumnMapToFieldMap(map);
//            Article article = null;
//            try {
//                article = MyUtils.refelctBean(fieldMap, Article.class);
//            } catch (Exception e) {
//                e.printStackTrace();
//                throw new TsinglogException(ResultEnum.SERVER_OCCURS_ERROR);
//            }
//            list.add(article);
//        }

        // 将List<Article>转为List<ArticleVo>
        List<ArticleVo> voList = this.transListArticleToListArticleVo(list, false);
        // 填充ArticleVo中的coverImage字段
        this.fillCoverImage(voList);

        PageInfo<Article> page = new PageInfo(list);
        Long total = page.getTotal();
        Integer row = page.getPages();

        latestArticlePage = new PageResult<ArticleVo>(total, row, voList);

        if (SystemConstant.INDEX_TYPE_RELEASE == type) { // 最近更新的文章
            operations.set(SystemConstant.REDIS_KEY_FOR_LATEST_RELEASE_ARTICLE, latestArticlePage);
        } else if  (SystemConstant.INDEX_TYPE_REPLY == type) { // 最近回复的文章
            operations.set(SystemConstant.REDIS_KEY_FOR_LATEST_REPLY_ARTICLE, latestArticlePage);
        }



        return latestArticlePage;
    }


    @Override
    public List<Map<String, Object>> getHotArticle() {

        ValueOperations operations = redisTemplate.opsForValue();

        if (redisTemplate.hasKey(SystemConstant.REDIS_KEY_FOR_HOT_ARTICLE)) {
            return (List<Map<String, Object>>) operations.get(SystemConstant.REDIS_KEY_FOR_HOT_ARTICLE);
        }

        // 固定查前8条数据
        PageHelper.startPage(SystemConstant.DEFAULT_CURRENT_PAGE, 8);
        List<Map<String, Object>> hotArticleList = articleMapper.getHotArticle();
        // 两个小时更新一次热门文章
        operations.set(SystemConstant.REDIS_KEY_FOR_HOT_ARTICLE, hotArticleList, 2, TimeUnit.HOURS);
        return hotArticleList;
    }

    @Override
    public PageDetail<ArticleVo> getArticleForSearch(Integer currentPage,
                                                     Integer pageSize,
                                                     Long cid,
                                                     Long tid,
                                                     String keyword) {

        if (currentPage == null) {
            currentPage = SystemConstant.DEFAULT_CURRENT_PAGE;
        }
        if (pageSize == null) {
            pageSize = SystemConstant.DEFAULT_PAGE_SIZE_FOR_LIST;
        }

        // cid、tid和keyword三个参数不能同时传递
        if ((cid != null && tid != null) || (cid != null && StringUtils.isNotBlank(keyword))
                || (StringUtils.isNotBlank(keyword) && tid != null)) {
            throw new TsinglogException(ResultCode.PARAM_IS_INVALID);
        }

        if (tid != null) {
            return this.getArticleByTag(currentPage, pageSize, tid);
        } else {
            Example example = new Example(Article.class);
            Example.Criteria criteria = example.createCriteria();

            if (cid != null) {
                criteria.andEqualTo("categoryId", cid);
            }
            if (StringUtils.isNotBlank(keyword)) {
                criteria.andLike("title", "%" + keyword + "%");
            }
            criteria.andEqualTo("isRelease", SystemConstant.USABLE_ENTITY_ON);
            criteria.andEqualTo("status", SystemConstant.STATUS_ENTITY_NORMAL);
            example.setOrderByClause("up_to_top desc, release_time desc");

            PageHelper.startPage(currentPage, pageSize);
            List<Article> articleList = articleMapper.selectByExample(example);

            List<ArticleVo> articleVos = this.transListArticleToListArticleVo(articleList, false);
            this.fillCoverImage(articleVos);
            PageInfo pageInfo = new PageInfo(articleList, SystemConstant.DEFAULT_SHOW_PAGE_NUM);

            int[] nums = pageInfo.getNavigatepageNums();
            List<Map<String, Object>> linkInfo = new ArrayList<>();
            PageDetail<ArticleVo> detail = new PageDetail<>();
            for (int i = 0; i < nums.length; i++) {
                Map<String, Object> map = new HashMap<>();
                int num = nums[i];
                map.put("num", num);
                if (cid != null) {
                    map.put("link", SystemConstant.PORTAL_SEARCH_URL
                            + "?page=" + num + "&cid=" + cid);
                    detail.setPreviousLink(SystemConstant.PORTAL_SEARCH_URL
                            + "?page=" + (currentPage - 1) + "&cid=" + cid);
                    detail.setNextLink(SystemConstant.PORTAL_SEARCH_URL
                            + "?page=" + (currentPage + 1) + "&cid=" + cid);
                    detail.setFirstLink(SystemConstant.PORTAL_SEARCH_URL
                            + "?page=1&cid=" + cid);
                    detail.setLastLink(SystemConstant.PORTAL_SEARCH_URL
                            + "?page=" + pageInfo.getPages() + "&cid=" + cid);
                } else if (StringUtils.isNotBlank(keyword)) {
                    map.put("link", SystemConstant.PORTAL_SEARCH_URL
                            + "?page=" + num + "&keyword=" + keyword);
                    detail.setPreviousLink(SystemConstant.PORTAL_SEARCH_URL
                            + "?page=" + (currentPage - 1) + "&keyword=" + keyword);
                    detail.setNextLink(SystemConstant.PORTAL_SEARCH_URL
                            + "?page=" + (currentPage + 1) + "&keyword=" + keyword);
                    detail.setFirstLink(SystemConstant.PORTAL_SEARCH_URL
                            + "?page=1" + "&keyword=" + keyword);
                    detail.setLastLink(SystemConstant.PORTAL_SEARCH_URL
                            + "?page=" + pageInfo.getPages() + "&keyword=" + keyword);
                } else {
                    map.put("link", SystemConstant.PORTAL_SEARCH_URL
                            + "?page=" + num);
                    detail.setPreviousLink(SystemConstant.PORTAL_SEARCH_URL
                            + "?page=" + (currentPage - 1));
                    detail.setNextLink(SystemConstant.PORTAL_SEARCH_URL
                            + "?page=" + (currentPage + 1));
                    detail.setFirstLink(SystemConstant.PORTAL_SEARCH_URL
                            + "?page=1");
                    detail.setLastLink(SystemConstant.PORTAL_SEARCH_URL
                            + "?page=" + pageInfo.getPages());
                }
                linkInfo.add(map);
            }

            detail.setData(articleVos);
            detail.setPageInfo(pageInfo);
            detail.setLinkInfo(linkInfo);

            return detail;
        }

    }

    @Override
    @Transactional
    public ArticleVo getArticleDetailForPortal(Long id) {

        // 1.查询文章的信息给前端页面
        ArticleVo vo = this.getArticleById(id);
        if (vo.getIsRelease() == SystemConstant.USABLE_ENTITY_OFF || vo.getStatus() == SystemConstant.STATUS_ENTITY_DELETE) {
            throw new TsinglogException(ResultCode.ARTICLE_STATUS_ERROR);
        }
        vo.setCoverImage(SystemConstant.IMAGE_PREFIX + vo.getCoverImage());
        vo.setArticleContent(vo.getArticleContent().replace(SystemConstant.PATH_SERVER_PLACEHOLDER, SystemConstant.IMAGE_PREFIX));

        // 2.增加文章的阅读数（改为让页面初始化时到后台去拿阅读数的数据）
        int result = this.insertArticleBrowse(id);
        if (result != 1) {
            log.error("增加id为{}的文章的浏览数失败！", id);
        } else {
            vo.setBrowseCount(vo.getBrowseCount() + 1);
        }
        return vo;
    }

    /**
     * 增加文章阅读数的记录
     * @param id
     */
    private int insertArticleBrowse(Long id) {
        String ip = IpUtils.getRealIp(RequestUtils.getRequest());
        return articleMapper.insertArticleBrowse(id, ip, getCurrentDateStr());
    }

    @Override
    public Map<String, Object> getArticleByPosition(Long id, int position) {

        Example example = new Example(Article.class);
        Example.Criteria criteria = example.createCriteria();

        criteria.andEqualTo("isRelease", SystemConstant.USABLE_ENTITY_ON);
        criteria.andEqualTo("status", SystemConstant.STATUS_ENTITY_NORMAL);

        example.setOrderByClause("release_time asc");

        List<Article> list = articleMapper.selectByExample(example);
        if (CollectionUtils.isEmpty(list)) {
            return new HashMap<>();
        }
        Map<String, Object> map = new HashMap<>();
        int index = -1;
        for (int i = 0; i < list.size(); i++) {
            Article _article = list.get(i);
            if (_article.getId() == id) {
                index = i;
                break;
            }
        }
        // 如果在数据库中的文章没有一篇的id为参数中的id，那么给前端返回id值为0，代表没有找到
        if (index == -1) {
            map.put("id", 0);
            return map;
        }

        // newIndex是新位置的索引
        int newIndex = index + position;
        // 如果移动的范围超过数组的大小，那么同样为返回id值为0，代表没有找到
        if (newIndex < 0 || newIndex > list.size() - 1) {
            map.put("id", 0);
            return map;
        }
        // 其他情况返回对应的文章信息
        Article article = list.get(newIndex);
        map.put("id", article.getId());
        map.put("title", article.getTitle());
        map.put("link", SystemConstant.PORTAL_ARTICLE_URL + article.getId());

        return map;
    }

    @Override
    @Transactional
    public Integer preferThisArticle(Long id) {

        if (id == null) {
            throw new TsinglogException(ResultCode.PARAM_IS_BLANK);
        }

        String key = IpUtils.getRealIp(RequestUtils.getRequest()) + SystemConstant.REDIS_PREFIX_FOR_DO_PREFER + id;
        if (redisTemplate.hasKey(key)) {
            throw new TsinglogException(ResultCode.DO_PREFER_ARTICLE_TOO_OFTEN);
        }

        Article dbArticle = articleMapper.selectArticleByPrimaryKeyForWeb(id);
        if (dbArticle == null) {
            throw new TsinglogException(ResultCode.HANDLE_DATA_NOT_EXISTED);
        }

        int result = this.insertArticlePrefer(id);
        if (result != 1) {
            log.error("增加id为{}的文章的喜欢数失败！", id);
        }

        // 将id写到redis中去，一个小时才能点赞一次
        redisTemplate.opsForValue().set(key, id, 1, TimeUnit.HOURS);

        return articleMapper.getPreferCountForArticle(id);
    }

    /**
     * 新增喜欢文章的记录
     * @param id 文章id
     */
    private int insertArticlePrefer(Long id) {
        String ip = IpUtils.getRealIp(RequestUtils.getRequest());
        return articleMapper.insertArticlePrefer(id, ip, getCurrentDateStr());
    }


    /**
     * 根据标签分页查询文章数据
     * @param currentPage
     * @param pageSize
     * @param tid
     */
    private PageDetail<ArticleVo> getArticleByTag(Integer currentPage, Integer pageSize, Long tid) {

        PageHelper.startPage(currentPage, pageSize);

        List<Map<String, Object>> list = articleMapper.getArticleByTag(tid);

        PageInfo pageInfo = new PageInfo(list, SystemConstant.DEFAULT_SHOW_PAGE_NUM);
        List<ArticleVo> voList = new ArrayList<>();
        if (!CollectionUtils.isEmpty(list)) {
            for (Map<String, Object> map : list) {
                ArticleVo vo = new ArticleVo();
                for (Map.Entry<String, Object> entry: map.entrySet()) {
                    if ("article_summary".equals(entry.getKey())) {
                        vo.setSummary((String) entry.getValue());
                    } else if ("id".equals(entry.getKey())) {
                        vo.setId(((Integer) entry.getValue()).longValue());
                    } else if ("cover_image".equals(entry.getKey())) {
                        vo.setCoverImage(SystemConstant.IMAGE_PREFIX + entry.getValue());
                    } else if ("title".equals(entry.getKey())) {
                        vo.setTitle((String) entry.getValue());
                    } else if ("type".equals(entry.getKey())) {
                        vo.setType(((Integer) entry.getValue()).shortValue());
                    } else if ("editor_type".equals(entry.getKey())) {
                        vo.setEditorType(((Integer) entry.getValue()).shortValue());
                    } else if ("release_time".equals(entry.getKey())) {
                        vo.setReleaseTime((String) entry.getValue());
                    }
                }
                this.setBrowseAndCommentAndPreferCountForVo(vo);
                voList.add(vo);
            }
        }

        List<Map<String, Object>> linkInfo = new ArrayList<>();
        int[] nums = pageInfo.getNavigatepageNums();
        for (int i = 0; i < nums.length; i++) {
            Map<String, Object> map = new HashMap<>();
            int num = nums[i];
            map.put("num", num);
            map.put("link", SystemConstant.PORTAL_SEARCH_URL
                    + "?page=" + num + "&tid=" + tid);
            linkInfo.add(map);
        }

        PageDetail<ArticleVo> detail = new PageDetail<>(pageInfo, voList, linkInfo);
        detail.setFirstLink(SystemConstant.PORTAL_SEARCH_URL
                + "?page=1&tid=" + tid);
        detail.setLastLink(SystemConstant.PORTAL_SEARCH_URL
                + "?page=" + pageInfo.getPages() + "&tid=" + tid);
        detail.setPreviousLink(SystemConstant.PORTAL_SEARCH_URL
                + "?page=" + (currentPage - 1) + "&tid=" + tid);
        detail.setNextLink(SystemConstant.PORTAL_SEARCH_URL
                + "?page=" + (currentPage + 1) + "&tid=" + tid);
        return detail;
    }


    /**
     * 将List<Article>转为List<ArticleVo>
     * @param list
     * @param isForServer 是否为后台提供数据，true为是，false为前台提供数据
     * @return
     */
    private List<ArticleVo> transListArticleToListArticleVo(List<Article> list, Boolean isForServer) {
        List<ArticleVo> listVo = new ArrayList<ArticleVo>();
        for (Article article: list) {
            ArticleVo vo = new ArticleVo(article);
            Category dbCategory = isForServer? categoryMapper.selectCategoryByPrimaryKeyForServer(vo.getCategoryId()) :
                    categoryMapper.selectCategoryByPrimaryKeyForWeb(vo.getCategoryId());

            if (dbCategory == null) {
                throw new TsinglogException(ResultCode.HANDLE_DATA_NOT_EXISTED);
            }
            String categoryName = dbCategory.getCategoryName();

            ArticleVo dbVo = this.getArticleById(vo.getId());

            vo.setCategoryName(categoryName);
            vo.setEditorType(dbVo.getEditorType());
            vo.setCoverImage(dbVo.getCoverImage());
            vo.setSummary(dbVo.getSummary());

            // 为vo设置browseCount、commentCount和preferCount
            this.setBrowseAndCommentAndPreferCountForVo(vo);

            listVo.add(vo);
        }
        return listVo;
    }

    /**
     * 为vo设置browseCount、commentCount和preferCount
     * @param vo
     */
    private void setBrowseAndCommentAndPreferCountForVo(ArticleVo vo) {
        int browseCount = articleMapper.getBrowseCountForArticle(vo.getId());
        int commentCount = vo.getCanComment() == 1? articleMapper.getCommentCountForArticle(vo.getId()): 0;
        int preferCount = articleMapper.getPreferCountForArticle(vo.getId());
        vo.setBrowseCount(browseCount);
        vo.setCommentCount(commentCount);
        vo.setPreferCount(preferCount);
    }

    /**
     * 填充ArticleVo中的coverImage字段，
     *      /6/8/68012426DB2342DFA0EAF1E879F50C93.jpg
     *      ->    http://image.tsinglog.online/6/8/68012426DB2342DFA0EAF1E879F50C93.jpg
     * @param list
     */
    private void fillCoverImage(List<ArticleVo> list) {
        for (ArticleVo vo: list) {
            vo.setCoverImage(SystemConstant.IMAGE_PREFIX + vo.getCoverImage());
        }
    }

}
